/*
 * ! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2017 by Hitachi Vantara : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 ******************************************************************************/

package org.pentaho.di.core.plugins;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import jdk.internal.misc.Unsafe;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.exception.KettlePluginClassMapException;
import org.pentaho.di.core.exception.KettlePluginException;
import org.pentaho.di.core.logging.KettleLogStore;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.logging.Metrics;
import org.pentaho.di.core.row.RowBuffer;
import org.pentaho.di.core.row.RowMeta;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.value.ValueMetaString;
import org.pentaho.di.core.util.EnvUtil;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.i18n.BaseMessages;
import org.yzbdl.lanius.authorize.verification.LicenseVerification;
import org.yzbdl.lanius.core.plugins.ClassLoaderManager;
import org.yzbdl.lanius.core.plugins.JarFileCache;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;

/**
 * This singleton provides access to all the plugins in the Kettle universe.<br> It allows you to register types and
 * plugins, query plugin lists per category, list plugins per type, etc.<br>
 *
 * @author matt
 */
public class PluginRegistry {

    private static final Class<?> PKG = PluginRegistry.class; // for i18n purposes, needed by Translator2!!

    private static final PluginRegistry pluginRegistry = new PluginRegistry();

    private static final List<PluginTypeInterface> pluginTypes = new ArrayList<PluginTypeInterface>();
    private static final List<PluginRegistryExtension> extensions = new ArrayList<PluginRegistryExtension>();
    public static final LogChannelInterface log = new LogChannel("PluginRegistry", true);
    public static final String SUPPLEMENTALS_SUFFIX = "-supplementals";

    /**
     * 插件类型与插件实例的映射集合, 目前已经要求插件的id+version具有唯一性，是否还需要做类型到插件的映射？
     */
    private final Map<Class<? extends PluginTypeInterface>, List<PluginInterface>> pluginMap = Maps.newHashMap();

    // private final Map<Class<? extends PluginTypeInterface>, List<String>> categoryMap = Maps.newHashMap();

    /**
     * 插件类型监听器, 监听插件类型下面所有插件的改动
     */
    private final Map<Class<? extends PluginTypeInterface>, List<PluginTypeListener>> listeners = Maps.newHashMap();

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * Initialize the registry, keep private to keep this a singleton
     */
    private PluginRegistry() {
    }

    /**
     * @return The one and only PluginRegistry instance
     */
    public static PluginRegistry getInstance() {
        return PluginRegistry.pluginRegistry;
    }

    // /**
    // * <pre>
    // * 注册插件类型：
    // * 1, 初始化插件类型与插件集合的映射
    // * 2, 初始化插件类型与插件分组的映射
    // * </pre>
    // *
    // * @param pluginType
    // */
    // private void registerPluginType(Class<? extends PluginTypeInterface> pluginType) {
    // lock.writeLock().lock();
    // try {
    // pluginMap.putIfAbsent(pluginType, Lists.newArrayList());
    //// categoryMap.putIfAbsent(pluginType, Lists.newArrayList());
    // } finally {
    // lock.writeLock().unlock();
    // }
    // }

    /**
     * <pre>
     * 移除指定插件类型的插件:
     * 1, 从pluginMap中移除插件
     * 2, 关闭插件对应的类加载器
     * 3, 移除插件的监听器
     * </pre>
     *
     * @param pluginType
     * @param plugin
     */
    public void removePlugin(Class<? extends PluginTypeInterface> pluginType, PluginInterface plugin) {
        lock.writeLock().lock();
        try {
            List<PluginInterface> list = pluginMap.get(pluginType);
            if (list != null) {
                list.remove(plugin);
            }
            ClassLoaderManager.getInstance().removeClassLoader(plugin);
        } finally {
            try {
                lock.writeLock().unlock();
                synchronized (this) {
                    notifyAll();
                }
                Optional.fromNullable(listeners.get(pluginType)).or(Lists.newArrayList()).forEach(list -> {
                    try {
                        list.pluginRemoved(plugin);
                    } catch (Exception e) {
                    }
                });

            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * <pre>
     * 设置插件中需要从父类加载器中加载的类:
     * 设置了该参数后, 该插件的加载就需要使用KettleSelectiveParentFirstClassLoader进行加载,
     * patterns为需要从父类加载器加载的类
     * </pre>
     *
     * @param plugin
     * @param patterns
     */
    public void addParentClassLoaderPatterns(PluginInterface plugin, String[] patterns) {
        ClassLoaderManager.getInstance().addParentClassLoaderPatterns(plugin, patterns);
    }

    /**
     * <pre>
     * 注册一个插件：
     * 1, 将插件按照顺序放入到pluginList中
     * 2, 如果是新增插件则添加插件的监听器, 如果是更新插件则更新插件的监听器
     * </pre>
     *
     * @param plugin
     * @param plugin
     * @throws KettlePluginException
     */
    public void registerPlugin(PluginInterface plugin) throws KettlePluginException {
        boolean changed = false;
        lock.writeLock().lock();
        Class<? extends PluginTypeInterface> pluginType = plugin.getPluginType();
        try {
            // 为了不影响启动速度 暂时只做非空判断
            if (ObjectUtil.isEmpty(LicenseVerification.getLicense()) && plugin.isEnterprise()) {
                PluginRegistry.log.logError("插件\"" + plugin.getName() + "\"只能在商业版中加载！");
                throw new KettlePluginException("插件\"" + plugin.getName() + "\"只能在商业版中加载！");
            }

            if (pluginType == null) {
                throw new KettlePluginException("插件的pluginType不合法 :" + pluginType);
            }

            if (StrUtil.isBlank(plugin.getId())) {
                throw new KettlePluginException("插件的id不合法 :" + plugin);
            }

            // 先注册插件类型
            List<PluginInterface> list = pluginMap.get(pluginType);
            if (list == null) {
                list = new ArrayList<PluginInterface>();
                pluginMap.put(pluginType, list);
            }

            // 插件是否存在(重复注册), 不存在则添加到list, 存在则替换list中的插件, 并保持位置不变.
            int index = list.indexOf(plugin);
            if (index < 0) {
                list.add(plugin);
            } else {
                list.set(index, plugin); // replace with the new one
                changed = true;
            }
            // 对插件list中的插件按照名称进行排序
            Collections.sort(list, (p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName()));
        } finally {
            lock.writeLock().unlock();

            if (pluginType != null) {
                List<PluginTypeListener> listeners = this.listeners.get(pluginType);
                if (listeners != null) {
                    for (PluginTypeListener listener : listeners) {
                        if (changed) {
                            listener.pluginChanged(plugin);
                        } else {
                            listener.pluginAdded(plugin);
                        }
                    }
                }
            }

            synchronized (this) {
                notifyAll();
            }
        }
    }

    // /**
    // * <pre>
    // * 注册一个插件：
    // * 1, 将插件按照顺序放入到pluginList中
    // * 2, 将插件的分组按顺序放入到categoryList中
    // * 3, 如果是新增插件则添加插件的监听器, 如果是更新插件则更新插件的监听器
    // * </pre>
    // *
    // * @param pluginType
    // * @param plugin
    // * @throws KettlePluginException
    // */
    // private void registerPlugin(Class<? extends PluginTypeInterface> pluginType, PluginInterface plugin)
    // throws KettlePluginException {
    // boolean changed = false; // Is this an add or an update?
    // lock.writeLock().lock();
    // try {
    // if (StrUtil.isBlank(plugin.getId())) {
    // throw new KettlePluginException("插件的ID不合法 :" + plugin);
    // }
    //
    // // 先注册插件类型
    // List<PluginInterface> list = pluginMap.get(pluginType);
    // if (list == null) {
    // list = new ArrayList<PluginInterface>();
    // pluginMap.put(pluginType, list);
    // }
    //
    // // 插件是否存在, 不存在则添加到list, 存在则替换list中的插件, 并保持位置不变.
    // int index = list.indexOf(plugin);
    // if (index < 0) {
    // list.add(plugin);
    // } else {
    // list.set(index, plugin); // replace with the new one
    // changed = true;
    // }
    //
    // // 对插件list中的插件按照名称进行排序
    // Collections.sort(list, (p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName()));
    //
    //// if (!Utils.isEmpty(plugin.getCategory())) {
    ////
    //// // 对该插件类型下的插件分组进行初始化
    //// List<String> categories = categoryMap.get(pluginType);
    //// if (categories == null) {
    //// categories = new ArrayList<String>();
    //// categoryMap.put(pluginType, categories);
    //// }
    ////
    //// if (!categories.contains(plugin.getCategory())) {
    //// categories.add(plugin.getCategory());
    ////
    //// // 根据PluginType的@PluginTypeCategoriesOrder注解对插件的分组进行排序.
    //// String[] naturalOrder = null;
    ////
    //// PluginTypeCategoriesOrder naturalOrderAnnotation = pluginType
    //// .getAnnotation(PluginTypeCategoriesOrder.class);
    ////
    //// if (naturalOrderAnnotation != null) {
    //// String[] naturalOrderKeys = naturalOrderAnnotation.getNaturalCategoriesOrder();
    //// Class<?> i18nClass = naturalOrderAnnotation.i18nPackageClass();
    //// naturalOrder = new String[naturalOrderKeys.length];
    //// for (int i = 0; i < naturalOrderKeys.length; i++) {
    //// naturalOrder[i] = BaseMessages.getString(i18nClass, naturalOrderKeys[i]);
    //// }
    //// }
    ////
    //// if (naturalOrder != null) {
    //// final String[] fNaturalOrder = naturalOrder;
    //// Collections.sort(categories,
    //// (c1, c2) -> Const.indexOfString(c1, fNaturalOrder) - Const.indexOfString(c2, fNaturalOrder));
    //// }
    //// }
    //// }
    // } finally {
    // lock.writeLock().unlock();
    // List<PluginTypeListener> listeners = this.listeners.get(pluginType);
    // if (listeners != null) {
    // for (PluginTypeListener listener : listeners) {
    // // Changed or added?
    // if (changed) {
    // listener.pluginChanged(plugin);
    // } else {
    // listener.pluginAdded(plugin);
    // }
    // }
    // }
    // synchronized (this) {
    // notifyAll();
    // }
    // }
    // }

    /**
     * 获取所有的插件类型，返回一个不可变的List集合。
     *
     * @return An unmodifiable list of plugin types
     */
    public List<Class<? extends PluginTypeInterface>> getPluginTypes() {
        lock.readLock().lock();
        try {
            return Collections.unmodifiableList(Lists.newArrayList(pluginMap.keySet()));
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * <pre>
     * 获取插件类型为type的所有插件实例
     * 使用linkhashset
     * </pre>
     *
     * @param type The plugin type to query
     * @return The list of plugins
     */
    public <K extends PluginTypeInterface> List<PluginInterface> getPlugins(Class<K> type) {
        LinkedHashSet<PluginInterface> set = new LinkedHashSet<>();
        lock.readLock().lock();
        try {
            pluginMap.entrySet().stream().filter(e -> Const.classIsOrExtends(e.getKey(), type)).map(Entry::getValue)
                    .forEach(list -> set.addAll(list));
        } finally {
            lock.readLock().unlock();
        }
        return new ArrayList<PluginInterface>(set);
    }

    /**
     * <pre>
     * 查找类型为pluginType, 插件ID为id, 插件版本为version的插件
     * Get a plugin from the registry
     * </pre>
     *
     * @param pluginType The type of plugin to look for
     * @param id         The ID to scan for
     * @return the plugin or null if nothing was found.
     */
    public PluginInterface getPlugin(Class<? extends PluginTypeInterface> pluginType, String id, String version) {
        if (Utils.isEmpty(id)) {
            return null;
        }

        // 暂时出现数据库为none无法向下兼容的情况
        if (pluginType.getName().equals("org.pentaho.di.core.plugins.DatabasePluginType")
                && (Utils.isEmpty(version) || version.equals("none"))) {
            version = Const.DEFAULT_VERSION;
        }

        if (Utils.isEmpty(version)) {
            version = "none";
        }

        final String ver = version;
        // getPlugins() never returns null, see his method above
        return getPlugins(pluginType).stream().filter(p -> p.getId().equalsIgnoreCase(id))
                .filter(p -> p.getVersion().equalsIgnoreCase(ver)).findFirst().orElse(null);
    }

    /**
     * <pre>
     * 根据插件类型和插件分组查找所有的插件实例, 返回一个不可变集合.
     * Retrieve a list of plugins per category.
     * </pre>
     *
     * @param pluginType     The type of plugins to search
     * @param pluginCategory The category to look in
     * @return An unmodifiable list of plugins that belong to the specified type and
     * category.
     */
    public <T extends PluginTypeInterface> List<PluginInterface> getPluginsByCategory(Class<T> pluginType,
                                                                                      String pluginCategory) {
        return getPlugins(pluginType).stream()
                .filter(p -> p.getCategory() != null && p.getCategory().equals(pluginCategory))
                .collect(Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf));
    }

    // /**
    // * Retrieve a list of all categories for a certain plugin type.
    // *
    // * @param pluginType The plugin type to search categories for.
    // * @return The list of categories for this plugin type. The list can be modified (sorted etc) but will not impact
    // the
    // * registry in any way.
    // */
    // private List<String> getCategories( Class<? extends PluginTypeInterface> pluginType ) {
    // lock.readLock().lock();
    // try {
    // return categoryMap.get( pluginType );
    // } finally {
    // lock.readLock().unlock();
    // }
    // }

    /**
     * <pre>
     * 加载插件中的mainClass, 返回实例
     *
     * Load and instantiate the main class of the plugin specified.
     * </pre>
     *
     * @param plugin The plugin to load the main class for.
     * @return The instantiated class
     * @throws KettlePluginException In case there was a loading problem.
     */
    public Object loadClass(PluginInterface plugin) throws KettlePluginException {
        return loadClass(plugin, plugin.getMainType());
    }

    /**
     * <pre>
     *
     *
     * Load the class of the type specified for the plugin that owns the class of
     * the specified object.
     * </pre>
     *
     * @param pluginType the type of plugin
     * @param object     The object for which we want to search the class to find
     *                   the plugin
     * @param classType  The type of class to load
     * @return the instantiated class.
     * @throws KettlePluginException
     */
    public <T> T loadClass(Class<? extends PluginTypeInterface> pluginType, Object object, Class<T> classType)
            throws KettlePluginException {
        PluginInterface plugin = getPlugin(pluginType, object);
        if (plugin == null) {
            return null;
        }
        return loadClass(plugin, classType);
    }

    /**
     * <pre>
     *
     *
     * Load the class of the type specified for the plugin that owns the class of
     * the specified object.
     * </pre>
     *
     * @param pluginType the type of plugin
     * @param object     The object for which we want to search the class to find
     *                   the plugin
     * @param classType  The type of class to load
     * @return the instantiated class.
     * @throws KettlePluginException
     */
    public <T> T loadClass(Class<? extends PluginTypeInterface> pluginType, Object object, Class<T> classType,
                           String version) throws KettlePluginException {
        PluginInterface plugin = getPlugin(pluginType, object, version);
        if (plugin == null) {
            return null;
        }
        return loadClass(plugin, classType);
    }

    /**
     * 加载指定类别和ID的插件类<br/>
     * Load the class of the type specified for the plugin with the ID specified.
     *
     * @param pluginType the type of plugin
     * @param pluginId   The plugin id to use
     * @param classType  The type of class to load
     * @return the instantiated class.
     * @throws KettlePluginException
     */
    public <T> T loadClass(Class<? extends PluginTypeInterface> pluginType, String pluginId, Class<T> classType)
            throws KettlePluginException {
        if (Utils.isEmpty(pluginId)) {
            return null;
        }
        PluginInterface plugin = getPlugin(pluginType, pluginId);
        if (plugin == null) {
            return null;
        }
        return loadClass(plugin, classType);
    }

    /**
     * Add a Class Mapping + factory for a plugin. This allows extra classes to be added to existing plugins.
     *
     * @param pluginType Type of plugin
     * @param tClass     Class to factory
     * @param id         ID of the plugin to extend
     * @param callable   Factory Callable
     * @param <T>        Type of the object factoried
     * @throws KettlePluginException
     */
    public <T> void addClassFactory(Class<? extends PluginTypeInterface> pluginType, Class<T> tClass, String id,
                                    Callable<T> callable) throws KettlePluginException {

        String key = createSupplemantalKey(pluginType.getName(), id);
        SupplementalPlugin supplementalPlugin = (SupplementalPlugin) getPlugin(pluginType, key);

        if (supplementalPlugin == null) {
            supplementalPlugin = new SupplementalPlugin(pluginType, key);
            registerPlugin(supplementalPlugin);
        }
        supplementalPlugin.addFactory(tClass, callable);
    }

    private String createSupplemantalKey(String pluginName, String id) {
        return pluginName + "-" + id + PluginRegistry.SUPPLEMENTALS_SUFFIX;
    }

    /**
     * 加载插件中指定的类<br/>
     * Load and instantiate the plugin class specified
     *
     * @param plugin      the plugin to load
     * @param pluginClass the class to be loaded
     * @return The instantiated class
     * @throws KettlePluginException In case there was a class loading problem somehow
     */
    @SuppressWarnings("unchecked")
    public <T> T loadClass(PluginInterface plugin, Class<T> pluginClass) throws KettlePluginException {
        if (plugin == null) {
            throw new KettlePluginException(
                    BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.RuntimeError.NoValidStepOrPlugin.PLUGINREGISTRY001"));
        }

        if (plugin instanceof ClassLoadingPluginInterface) {
            return ((ClassLoadingPluginInterface) plugin).loadClass(pluginClass);
        } else {

            String className = plugin.getClassMap().get(pluginClass);
            if (className == null) {
                // Look for supplemental plugin supplying extra classes
                String id = plugin.getId();
                // for ( String id : plugin.getIds() ) {
                try {
                    T aClass = loadClass(plugin.getPluginType(),
                            createSupplemantalKey(plugin.getPluginType().getName(), id), pluginClass);
                    if (aClass != null) {
                        return aClass;
                    }
                } catch (KettlePluginException exception) {
                    // ignore. we'll fall through to the other exception if this loop doesn't produce a return
                }
                // }
                throw new KettlePluginClassMapException(BaseMessages.getString(PluginRegistry.PKG,
                        "PluginRegistry.RuntimeError.NoValidClassRequested.PLUGINREGISTRY002", pluginClass.getName()));
            }

            try {
                Class<? extends T> cl;
                if (plugin.isNativePlugin()) {
                    cl = (Class<? extends T>) Class.forName(className);
                } else {
                    URLClassLoader ucl = (URLClassLoader) ClassLoaderManager.getInstance().getClassLoader(plugin);
                    cl = (Class<? extends T>) ucl.loadClass(className);
                }

                try {
                    return cl.newInstance();
                } catch (InstantiationException e) {
                    Field unsafeF = Unsafe.class.getDeclaredField("theUnsafe");
                    unsafeF.setAccessible(true);
                    Unsafe unsafe = (Unsafe) unsafeF.get(Unsafe.class);
                    return (T) unsafe.allocateInstance(cl);
                }

            } catch (ClassNotFoundException e) {
                throw new KettlePluginException(
                        BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.RuntimeError.ClassNotFound.PLUGINREGISTRY003"), e);
            } catch (InstantiationException e) {
                throw new KettlePluginException(BaseMessages.getString(PluginRegistry.PKG,
                        "PluginRegistry.RuntimeError.UnableToInstantiateClass.PLUGINREGISTRY004"), e);
            } catch (IllegalAccessException e) {
                throw new KettlePluginException(
                        BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.RuntimeError.IllegalAccessToClass.PLUGINREGISTRY005"),
                        e);
            } catch (Throwable e) {
                e.printStackTrace();
                throw new KettlePluginException(BaseMessages.getString(PluginRegistry.PKG,
                        "PluginRegistry.RuntimeError.UnExpectedErrorLoadingClass.PLUGINREGISTRY007"), e);
            }
        }
    }

    /**
     * Add a PluginType to be managed by the registry
     *
     * @param type
     */
    public static void addPluginType(PluginTypeInterface type) {
        PluginRegistry.pluginTypes.add(type);
    }

    /**
     * Added so we can tell when types have been added (but not necessarily registered)
     *
     * @return the list of added plugin types
     */
    public static List<PluginTypeInterface> getAddedPluginTypes() {
        return Collections.unmodifiableList(PluginRegistry.pluginTypes);
    }

    /**
     * This method registers plugin types and loads their respective plugins
     *
     * @throws KettlePluginException
     */
    public static void init() throws KettlePluginException {
        PluginRegistry.registerPluginRegistryExtension();
        PluginRegistry.registerPluginType();
        // Clear the jar file cache so that we don't waste memory...
        JarFileCache.getInstance().clear();
    }

    /**
     * This method registers plugin types and loads their respective plugins
     *
     * @throws KettlePluginException
     */
    public static void init(List<PluginTypeInterface> pluginClasses) throws KettlePluginException {
        PluginRegistry.registerPluginRegistryExtension();
        PluginRegistry.registerPluginType();
        // Clear the jar file cache so that we don't waste memory...
        JarFileCache.getInstance().clear();
    }

    /**
     * 注册PluginRegistryExtension
     */
    private static void registerPluginRegistryExtension() {
        final PluginRegistry registry = PluginRegistry.getInstance();
        PluginTypeInterface pluginType = PluginRegistryPluginType.getInstance();
        // 防止重复注册
        if (!registry.pluginMap.containsKey(pluginType.getClass())) {
            PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_REGISTER_EXTENSIONS_START);
            // Find pluginRegistry extensions
            try {
                registry.registerType(pluginType);
                List<PluginInterface> plugins = registry.getPlugins(PluginRegistryPluginType.class);

                for (PluginInterface extensionPlugin : plugins) {

                    PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_REGISTER_EXTENSION_START, extensionPlugin.getName());
                    PluginRegistryExtension extension = (PluginRegistryExtension) registry.loadClass(extensionPlugin);

                    extension.init(registry);
                    PluginRegistry.extensions.add(extension);

                    PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_REGISTER_EXTENSION_STOP, extensionPlugin.getName());
                }
            } catch (KettlePluginException e) {
                e.printStackTrace();
            }
            PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_REGISTER_EXTENSIONS_STOP);
        }
    }

    private static void registerPluginType() throws KettlePluginException {
        final PluginRegistry registry = PluginRegistry.getInstance();
        PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_PLUGIN_REGISTRATION_START);
        for (final PluginTypeInterface pluginType : PluginRegistry.pluginTypes) {
            if (!registry.hasRegister(pluginType)) {
                PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_PLUGIN_TYPE_REGISTRATION_START, pluginType.getName());
                registry.registerType(pluginType);
                PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_PLUGIN_TYPE_REGISTRATION_STOP, pluginType.getName());
            }
        }
        PluginRegistry.log.snap(Metrics.METRIC_PLUGIN_REGISTRY_PLUGIN_REGISTRATION_STOP);
    }

    /**
     * <pre>
     * 注册插件类型:
     * 1,
     *
     * </pre>
     *
     * @param pluginType
     * @throws KettlePluginException
     */
    private void registerType(PluginTypeInterface pluginType) throws KettlePluginException {
        // registerPluginType( pluginType.getClass() );
        pluginMap.putIfAbsent(pluginType.getClass(), Lists.newArrayList());

        // Search plugins for this type...
        //
        long startScan = System.currentTimeMillis();
        pluginType.searchPlugins();

        for (PluginRegistryExtension ext : PluginRegistry.extensions) {
            ext.searchForType(pluginType);
        }

        List<String> pluginClassNames = new ArrayList<String>();

        // Scan for plugin classes to facilitate debugging etc.
        //
        String pluginClasses = EnvUtil.getSystemProperty(Const.KETTLE_PLUGIN_CLASSES);
        if (!Utils.isEmpty(pluginClasses)) {
            String[] classNames = pluginClasses.split(",");

            for (String className : classNames) {
                if (!pluginClassNames.contains(className)) {
                    pluginClassNames.add(className);
                }
            }
        }

        for (String className : pluginClassNames) {
            try {
                // What annotation does the plugin type have?
                //
                PluginAnnotationType annotationType = pluginType.getClass().getAnnotation(PluginAnnotationType.class);
                if (annotationType != null) {
                    Class<? extends Annotation> annotationClass = annotationType.value();

                    Class<?> clazz = Class.forName(className);
                    Annotation annotation = clazz.getAnnotation(annotationClass);

                    if (annotation != null) {
                        // Register this one!
                        //
                        pluginType.handlePluginAnnotation(clazz, annotation, new ArrayList<String>(), true, null);
                        LogChannel.GENERAL.logBasic(
                                "Plugin class " + className + " registered for plugin type '" + pluginType.getName() + "'");
                    } else {
                        if (KettleLogStore.isInitialized() && LogChannel.GENERAL.isDebug()) {
                            LogChannel.GENERAL.logDebug("Plugin class " + className
                                    + " doesn't contain annotation for plugin type '" + pluginType.getName() + "'");
                        }
                    }
                } else {
                    if (KettleLogStore.isInitialized() && LogChannel.GENERAL.isDebug()) {
                        LogChannel.GENERAL.logDebug("Plugin class " + className
                                + " doesn't contain valid class for plugin type '" + pluginType.getName() + "'");
                    }
                }
            } catch (Exception e) {
                if (KettleLogStore.isInitialized()) {
                    LogChannel.GENERAL.logError("Error registring plugin class from KETTLE_PLUGIN_CLASSES: " + className
                            + Const.CR + Const.getStackTracker(e));
                }
            }
        }

        if (LogChannel.GENERAL.isDetailed()) {
            LogChannel.GENERAL
                    .logDetailed("Registered " + getPlugins(pluginType.getClass()).size() + " plugins of type '"
                            + pluginType.getName() + "' in " + (System.currentTimeMillis() - startScan) + "ms.");
        }

    }

    /**
     * 根据一个插件类实例对象获取插件的ID <br/>
     * Find the plugin ID based on the class
     *
     * @param classInstanceInPlugin 插件中的任何一个类实例
     * @return The ID of the plugin to which this class belongs (checks the plugin
     * class maps)
     */
    @Deprecated
    public String getPluginId(Object classInstanceInPlugin) {
        return getPluginId(classInstanceInPlugin, Const.DEFAULT_VERSION);
    }

    /**
     * 根据一个插件类实例对象获取插件的ID <br/>
     * Find the plugin ID based on the class
     *
     * @param classInstanceInPlugin 插件中的任何一个类实例
     * @return The ID of the plugin to which this class belongs (checks the plugin
     * class maps)
     */
    public String getPluginId(Object classInstanceInPlugin, String version) {
        for (Class<? extends PluginTypeInterface> pluginType : getPluginTypes()) {
            String id = getPluginId(pluginType, classInstanceInPlugin, version);
            if (id != null) {
                return id;
            }
        }
        return null;
    }

    /**
     * <pre>
     * 根据插件类别和插件中某个类实例查找插件的ID, version=none
     *
     * Find the plugin ID based on the class
     * </pre>
     *
     * @param pluginType            the type of plugin
     * @param classInstanceInPlugin The class to look for
     * @return The ID of the plugin to which this class belongs (checks the plugin class maps) or null if nothing was
     * found.
     */
    @Deprecated
    public String getPluginId(Class<? extends PluginTypeInterface> pluginType, Object classInstanceInPlugin) {
        return getPluginId(pluginType, classInstanceInPlugin, Const.DEFAULT_VERSION);
    }

    /**
     * <pre>
     * 根据插件类别/插件中某个类实例/version查找插件的ID
     * Find the plugin ID based on the class
     * </pre>
     *
     * @param pluginType            the type of plugin
     * @param classInstanceInPlugin The class to look for
     * @return The ID of the plugin to which this class belongs (checks the plugin
     * class maps) or null if nothing was found.
     */
    public String getPluginId(Class<? extends PluginTypeInterface> pluginType, Object classInstanceInPlugin,
                              String version) {
        String className = classInstanceInPlugin.getClass().getName();

        // 先从PluginRegistry中找
        for (PluginInterface plugin : getPlugins(pluginType)) {
            for (String check : plugin.getClassMap().values()) {
                if (check != null && check.equals(className) && version.equals(plugin.getVersion())) {
                    return plugin.getId();
                }
            }
        }

        // 如果找不到, 则从PluginRegistryExtension中找
        for (PluginRegistryExtension ext : PluginRegistry.extensions) {
            String id = ext.getPluginId(pluginType, classInstanceInPlugin);
            if (id != null) {
                return id;
            }
        }
        return null;
    }

    /**
     * <pre>
     * 根据指定的对象实例查找插件, 方法有缺陷
     * 1,如果同一个id不同版本的插件, 如何区分？
     * 2,如果不同id插件出现相同的类, 如何区分？
     *
     * Retrieve the Plugin for a given class
     * </pre>
     *
     * @param pluginType            The type of plugin to search for
     * @param classInstanceInPlugin The class of this object is used to look around
     * @return the plugin or null if nothing could be found
     */
    @Deprecated
    private PluginInterface getPlugin(Class<? extends PluginTypeInterface> pluginType, Object classInstanceInPlugin) {
        if (classInstanceInPlugin == null) {
            return null;
        }
        return getPlugin(pluginType, classInstanceInPlugin, "none");
    }

    /**
     * <pre>
     * 根据指定的对象实例查找插件
     *
     * Retrieve the Plugin for a given class
     * </pre>
     *
     * @param pluginType            The type of plugin to search for
     * @param classInstanceInPlugin The class of this object is used to look around
     * @return the plugin or null if nothing could be found
     */
    public PluginInterface getPlugin(Class<? extends PluginTypeInterface> pluginType, Object classInstanceInPlugin,
                                     String version) {
        String pluginId = getPluginId(pluginType, classInstanceInPlugin, version);
        if (pluginId == null) {
            return null;
        }
        return findPluginWithId(pluginType, pluginId, version);
    }

    /**
     * Get a plugin from the registry
     *
     * @param pluginType The type of plugin to look for
     * @param id         The ID to scan for
     * @return the plugin or null if nothing was found.
     */
    public PluginInterface getPlugin(Class<? extends PluginTypeInterface> pluginType, String id) {
        if (Utils.isEmpty(id)) {
            return null;
        }
        return findPluginWithId(pluginType, id, Const.DEFAULT_VERSION);
    }

    /**
     * Find the plugin ID based on the name of the plugin
     *
     * @param pluginType the type of plugin
     * @param pluginName The name to look for
     * @return The plugin with the specified name or null if nothing was found.
     */
    public PluginInterface getPluginWithName(Class<? extends PluginTypeInterface> pluginType, String pluginName) {
        for (PluginInterface plugin : getPlugins(pluginType)) {

            if (plugin.getName().equals(pluginName)) {
                return plugin;
            }
        }
        return null;
    }

    /**
     * Find the plugin ID based on the description of the plugin
     *
     * @param pluginType        the type of plugin
     * @param pluginDescription The description to look for
     * @return The plugin with the specified description or null if nothing was found.
     */
    public PluginInterface getPluginWithDescription(Class<? extends PluginTypeInterface> pluginType,
                                                    String pluginDescription) {
        for (PluginInterface plugin : getPlugins(pluginType)) {

            if (plugin.getDescription().equals(pluginDescription)) {
                return plugin;
            }
        }
        return null;
    }

    /**
     * <pre>
     * 根据插件id和类型查找插件， 由于未精确到版本，此方法返回version=none的插件.
     * Find the plugin ID based on the name of the plugin
     * </pre>
     *
     * @param pluginType the type of plugin
     * @param pluginId   The name to look for
     * @return The plugin with the specified name or null if nothing was found.
     */
    @Deprecated
    private PluginInterface findPluginWithId(Class<? extends PluginTypeInterface> pluginType, String pluginId) {
        return findPluginWithId(pluginType, pluginId, "none");
    }

    /**
     * <pre>
     * 根据插件id和类型查找插件
     * Find the plugin ID based on the name of the plugin
     * </pre>
     *
     * @param pluginType the type of plugin
     * @param pluginId   The name to look for
     * @return The plugin with the specified name or null if nothing was found.
     */
    private PluginInterface findPluginWithId(Class<? extends PluginTypeInterface> pluginType, String pluginId,
                                             String version) {
        version = version.equals("none") ? Const.DEFAULT_VERSION : version;
        for (PluginInterface plugin : getPlugins(pluginType)) {
            if (plugin.getId().equals(pluginId) && version.equals(plugin.getVersion())) {
                return plugin;
            }
        }
        return null;
    }

    /**
     * <pre>
     * 根据插件id、版本和类型查找插件
     * </pre>
     *
     * @param pluginType
     * @param pluginId
     * @param version
     * @return
     */
    public PluginInterface findPlugin(Class<? extends PluginTypeInterface> pluginType, String pluginId,
                                      String version) {
        for (PluginInterface plugin : getPlugins(pluginType)) {
            if (plugin.getId().equals(pluginId) && plugin.getVersion().equals(version)) {
                return plugin;
            }
        }
        return null;
    }

    /**
     * @return a unique list of all the step plugin package names
     */
    public List<String> getPluginPackages(Class<? extends PluginTypeInterface> pluginType) {
        List<String> list = new ArrayList<String>();
        for (PluginInterface plugin : getPlugins(pluginType)) {
            for (String className : plugin.getClassMap().values()) {
                int lastIndex = className.lastIndexOf(".");
                if (lastIndex > -1) {
                    String packageName = className.substring(0, lastIndex);
                    if (!list.contains(packageName)) {
                        list.add(packageName);
                    }
                }
            }
        }
        Collections.sort(list);
        return list;
    }

    private RowMetaInterface getPluginInformationRowMeta() {
        RowMetaInterface row = new RowMeta();

        row.addValueMeta(new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.Type.Label")));
        row.addValueMeta(new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.ID.Label")));
        row.addValueMeta(new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.Name.Label")));
        row.addValueMeta(
                new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.Description.Label")));
        row.addValueMeta(
                new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.Libraries.Label")));
        row.addValueMeta(
                new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.ImageFile.Label")));
        row.addValueMeta(
                new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.ClassName.Label")));
        row.addValueMeta(new ValueMetaString(BaseMessages.getString(PluginRegistry.PKG, "PluginRegistry.Information.Category.Label")));

        return row;
    }

    /**
     * @param pluginType the type of plugin to get information for
     * @return a row buffer containing plugin information for the given plugin type
     * @throws KettlePluginException
     */
    public RowBuffer getPluginInformation(Class<? extends PluginTypeInterface> pluginType)
            throws KettlePluginException {
        RowBuffer rowBuffer = new RowBuffer(getPluginInformationRowMeta());
        for (PluginInterface plugin : getPlugins(pluginType)) {

            Object[] row = new Object[getPluginInformationRowMeta().size()];
            int rowIndex = 0;

            row[rowIndex++] = getPluginType(plugin.getPluginType()).getName();
            row[rowIndex++] = plugin.getId();
            row[rowIndex++] = plugin.getName();
            row[rowIndex++] = Const.NVL(plugin.getDescription(), "");
            row[rowIndex++] = Utils.isEmpty(plugin.getLibraries()) ? "" : plugin.getLibraries().toString();
            row[rowIndex++] = Const.NVL(plugin.getImageFile(), "");
            row[rowIndex++] = plugin.getClassMap().values().toString();
            row[rowIndex++] = Const.NVL(plugin.getCategory(), "");

            rowBuffer.getBuffer().add(row);
        }
        return rowBuffer;
    }

    /**
     * Load the class with a certain name using the class loader of certain plugin.
     *
     * @param plugin    The plugin for which we want to use the class loader
     * @param className The name of the class to load
     * @return the name of the class
     * @throws KettlePluginException In case there is something wrong
     */
    @SuppressWarnings("unchecked")
    public <T> T getClass(PluginInterface plugin, String className) throws KettlePluginException {
        try {
            if (plugin.isNativePlugin()) {
                return (T) Class.forName(className);
            } else {
                URLClassLoader ucl = (URLClassLoader) ClassLoaderManager.getInstance().getClassLoader(plugin);
                if (ucl == null) {
                    throw new KettlePluginException("Unable to find class loader for plugin: " + plugin);
                }
                return (T) ucl.loadClass(className);

            }
        } catch (Exception e) {
            throw new KettlePluginException("Unexpected error loading class with name: " + className, e);
        }
    }

    /**
     * Load the class with a certain name using the class loader of certain plugin.
     *
     * @param plugin    The plugin for which we want to use the class loader
     * @param classType The type of class to load
     * @return the name of the class
     * @throws KettlePluginException In case there is something wrong
     */
    @SuppressWarnings("unchecked")
    public <T> T getClass(PluginInterface plugin, T classType) throws KettlePluginException {
        String className = plugin.getClassMap().get(classType);
        return (T) getClass(plugin, className);
    }

    /**
     * <pre>
     * 添加用于监听插件的新增、移除、更新的监听器
     * Allows the tracking of plugins as they come and go.
     * </pre>
     *
     * @param typeToTrack extension of PluginTypeInterface to track.
     * @param listener    receives notification when a plugin of the specified type
     *                    is added/removed/modified
     * @param <T>         extension of PluginTypeInterface
     */
    public <T extends PluginTypeInterface> void addPluginListener(Class<T> typeToTrack, PluginTypeListener listener) {
        lock.writeLock().lock();
        try {
            List<PluginTypeListener> list = listeners.get(typeToTrack);
            if (list == null) {
                list = new ArrayList<PluginTypeListener>();
                listeners.put(typeToTrack, list);
            }
            if (!list.contains(listener)) {
                list.add(listener);
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 获取pluginTypeClass实例
     *
     * @param pluginTypeClass
     * @return
     * @throws KettlePluginException
     */
    public PluginTypeInterface getPluginType(Class<? extends PluginTypeInterface> pluginTypeClass)
            throws KettlePluginException {
        try {
            Method method = pluginTypeClass.getMethod("getInstance", new Class<?>[0]);
            return (PluginTypeInterface) method.invoke(null, new Object[0]);
        } catch (Exception e) {
            throw new KettlePluginException("Unable to get instance of plugin type: " + pluginTypeClass.getName(), e);
        }
    }

    // private List<PluginInterface> findPluginsByFolder( URL folder ) {
    // String path = folder.getPath();
    // try {
    // path = folder.toURI().normalize().getPath();
    // } catch ( URISyntaxException e ) {
    // log.logError( e.getLocalizedMessage(), e );
    // }
    // if ( path.endsWith( Const.FILE_SEPARATOR ) ) {
    // path = path.substring( 0, path.length() - 1 );
    // }
    // List<PluginInterface> result = new ArrayList<PluginInterface>();
    // lock.readLock().lock();
    // try {
    // for ( List<PluginInterface> typeInterfaces : pluginMap.values() ) {
    // for ( PluginInterface plugin : typeInterfaces ) {
    // URL pluginFolder = plugin.getPluginDirectory();
    // try {
    // if ( pluginFolder != null && pluginFolder.toURI().normalize().getPath().startsWith( path ) ) {
    // result.add( plugin );
    // }
    // } catch ( URISyntaxException e ) {
    // log.logError( e.getLocalizedMessage(), e );
    // }
    // }
    // }
    // } finally {
    // lock.readLock().unlock();
    // }
    // return result;
    // }

    /**
     * 判断是否已经注册过该插件类型
     *
     * @param pluginType
     * @return
     */
    public boolean hasRegister(PluginTypeInterface pluginType) {
        return pluginMap.containsKey(pluginType.getClass());
    }

    // Note - This method is only called by KettleClientEnvironment.reset() which is
    // only called by test cases.
    // Otherwise, a JIRA case would be warranted as a non-static method is clearing
    // the contents of two static variables ( pluginTypes and extensions ). This is
    // problematic under any other circumstance.
    public void reset() {
        lock.writeLock().lock();
        try {
            PluginRegistry.pluginTypes.clear();
            PluginRegistry.extensions.clear();
            pluginMap.clear();
            // classLoaderMap.clear();
            // classLoaderGroupsMap.clear();
            // folderBasedClassLoaderMap.clear();
            // inverseClassLoaderLookup.forEach( ( key, value ) -> {
            // try {
            // key.close();
            // } catch ( IOException e ) {
            // e.printStackTrace();
            // }
            // } );
            // inverseClassLoaderLookup.clear();
            // categoryMap.clear();
            // parentClassloaderPatternMap.clear();
            listeners.clear();
            JarFileCache.getInstance().clear();
        } finally {
            lock.writeLock().unlock();
        }
    }
}
